﻿using System;
using System.Collections.Generic;
using System.Text;

namespace LinearAlgebra
{
	/// <summary>
	/// A function that returns a value for a given position in a matrix
	/// Is intentionally vague, it can be used for many things, and thanks to C#
	/// Can in some instances be made context aware, or sequentially aware
	/// Woot for closures!
	/// </summary>
	/// <param name="i">The ith Row</param>
	/// <param name="j">The jth Column</param>
	/// <returns>A value @ Mat[i,j]</returns>
	public delegate double MatrixFunction( int i, int j );

	public enum MatrixLiteralFormat
	{	//doubt I'll ever implement these, but it is a nice idea if someone is going to interop with any of these programs to have them
		Mathematica,	//FUTURE: pass any of these to a ToStringLiteral( MatrixStringLiteralFormat )
		MATLAB,
		Maple,
		CSharp,
		ECMAScript,	//should be the same as Java
		Java			//should be the same as ECMAScript
	}

	public class Matrix
	{
		public List<double[]> Rows; //allows for foreachs in a way where the element exposed is obvious, better than implementing IEnumerable

		public Matrix( int n ) //constructor
		{
			Rows = new List<double[]>( n );
			for( int i = 0; i < n; i++ )
				Rows.Add( new double[n] ); //add an array of double for each row
		}

		//I don't know If I should abstract this or not...I have feelings both ways, abstracting it won't limit us ever, so I think I should ultimately do it that way...
		public double[] this[int i] //returns a row of values!
		{
			get {
				return this.Rows[i];
			}
			set {
				this.Rows[i] = value; //don't know if this should be allowed, but it would make swapping rows easy
			}
		}

		private int Length //Compatibility with Regular
		{
			get {
				return this.Rows.Count;
			}
		}

		/// <summary>
		/// Returns a matrix of all zeroes
		/// </summary>
		/// <param name="n">The number of rows</param>		
		/// <returns>A Matrix of Zeroes, the Zero matrix</returns>
		public static Matrix GetZeroMatrix( int n )
		{
			Matrix A = new Matrix( n ); /* the matrix A */
			for( int i = 0; i < n; i++ )
			{	//for each row
				A[i] = new double[n]; //create an array of doubles
				for( int j = 0; j < n; j++ )
             		A[i][j] = 0; //assign zero to every element
			}
			return A;
		}
		/// <summary>
		/// Returns a matrix that is a function of the MatrixFunction{i,j} applied to each cell of the matrix
		/// </summary>		
		public static Matrix GetMatrix( int n, MatrixFunction mf )
		{
			Matrix m = new Matrix( n ); /* the matrix A */
			for( int i = 0; i < n; i++ )
			{
				m[i] = new double[n]; //create the nested array
				for( int j = 0; j < n; j++ )
					m[i][j] = mf( i, j ); //initialize it using the matrix function 
			}
			return m;
		}		
		/// <summary>
		/// Prompts the user at the console to input a matrix
		/// </summary>
		/// <param name="n">Size of the Matrix</param>		
		public static Matrix GetMatrixFromUser( int n )
		{
			Matrix m = GetMatrix( n, delegate( int i, int j ) { //give the [start][j] slot of the matrix a random integer value between 1 and 100
				return double.Parse( Prompt( String.Format( "[{0}][{1}] = ", i, j ) ) );
			} );
			return m;
		}	
		/// <summary>
		/// Returns a Matrix where each M(i == j) = 1, and each M(i != j) = 0
		/// </summary>
		/// <param name="n">The size of the matrix; the number of elements it contains</param>		
		public static Matrix GetIdentityMatrix( int n ) //parallelized method to generate identity matrix
		{
			Matrix matrix = GetMatrix( n, delegate ( int i, int j ) { //the eventual resultant identity matrix
				if( i == j )	//sets 1's
					return 1;	//for the diagonal
				return 0;		
			} );
			return matrix;
		}
		/// <summary>
		/// Gets a random matrix using the seed, and upper and lower boundaries specified by the arguments
		/// </summary>
		/// <param name="n">Size of the Mat</param>
		/// <param name="seed">The Seed provided to the random number generator</param>
		/// <param name="lower">The lower bound of the random number generator(inclusive)</param>
		/// <param name="upper">The upper bound of the random number generator(exclusive)</param>
		/// <returns>A Random Matrix</returns>
		public static Matrix GetRandomMatrix( int n, int seed, int lower, int upper )
		{			
			Random r = new Random( seed ); //we don't want the numbers to be different, this way we can test easier
			Matrix m = GetMatrix( n, delegate( int i, int j ) {
				//pass in a random matrix function to the GetMatrix, not sure if it will fail without the params, but it seems like it should!
				return r.Next( lower, upper );  //give the [start,j] slot of the matrix a random integer value between 1 and 100, is automatically cast as a double
			} );
			return m;
		}		

		//operations
		public static void AddColumnMultiple( double[][] A, double[][] B, double mult, int from, int to )
		{
			for( int i = 0; i < A.Length; i++ ) { //this doesn't seem right...
				B[to][i] = B[to][i] + B[from][i] * mult;				
			}
		}
		public static void AddRowMultiple( double[][] A, double[][] B, double mult, int from, int to )
		{
			for( int i = 0; i < A.Length; i++ ) {
				A[to][i] = A[to][i] + A[from][i] * mult;
				B[to][i] = B[to][i] + B[from][i] * mult;
			}
		}

		/// <summary>
		/// Tests for binary equality in two matrices with elements of type double		
		/// <returns></returns>
		public static bool operator==( Matrix A, Matrix B  )
		{
			return AreMatricesEqual( A, B, double.Epsilon );
		}

		public static bool operator !=(Matrix A, Matrix B)
		{
			return !(A == B); //neat!
		}

		/// <summary>
		/// Compares two matrices using a maximum allowable difference(epsilon)
		/// </summary>
		/// <param name="A">The first matrix</param>
		/// <param name="B">The second matrix</param>
		/// <param name="maxdiff">The tolerance between values at the same index</param>
		/// <returns>A boolean value indicating a positive assertion of equality</returns>
		public static bool AreMatricesEqual( Matrix A, Matrix B, double maxdiff )
		{
			if( A.Length != B.Length )
				return false;
			for( int i = 0; i < A.Length; i++ )
			{
				if( A[i].Length != B[i].Length ) //if there is jaggedness
					return false;
				double diff;
				for( int j = 0; j < A[0].Length; j++ )
				{
					diff = Math.Abs( A[i][j] - B[i][j] );
					if( diff > maxdiff )
						maxdiff = diff;
					if( diff > .0001 )
						return false;
				}
			}
			return true;
		}

		/// <summary>
		/// Writes the matrix to console in a very nice and readable format, showing 5 places beyond the decimal
		/// </summary>
		/// <param name="matrix">The matrix to be written to the console</param>
		public static void Write( double[][] matrix ) { Write( matrix, "f5" ); }

		/// <summary>
		/// Writes out the matrix using whatever format string you desire
		/// </summary>
		/// <param name="matrix">The matrix</param>
		/// <param name="formatString"> The format string given to the ToString method See ToString( string formatString ) for more info </param>
		public static void Write( double[][] matrix, string formatString )
		{			
			int[] colMaxes = new int[matrix.Length]; //number of columns in the first row
			for( int j = 0; j < matrix.Length; j++ ) //for each column
			{
				int columnWidth = 0; //the amount of padding to apply
				for( int i = 0; i < matrix.Length; i++ )
				{
					int curValLen = matrix[i][j].ToString(formatString).Length;
					if( curValLen > columnWidth )
						columnWidth = curValLen;					
				}
				if( j != matrix.Length-1 )	//if j is not the last element															
					columnWidth += 2;			//the space and a comma				
				colMaxes[j] = columnWidth; //the current columns max width
			}
			//now we have the col maxes			
			Console.WriteLine( "" );
			for( int i = 0; i < matrix.Length; i++ )
			{	
				Console.Write( " [ " );
				for( int j = 0; j < matrix.Length; j++ )
				{				
					string value = matrix[i][j].ToString(formatString);
					int valueLen = value.Length;
					if( j != matrix.Length -1 ) //if j is not the last element
						value += ", ";

					Console.Write( value.PadLeft( colMaxes[j], ' ' ) );
				}
				Console.Write( " ] " + Environment.NewLine );
			}
		}

		/// <summary>
		/// Returns a string Literal Compatible with Mathematica Matrix String Literal format
		/// This is very useful when you need to pass a matrix to mathematica
		/// </summary>		
		/// <returns>Mathematica Compatible Matrix String Literal</returns>
		public string ToMathematicaLiteral()
		{
			StringBuilder sb = new StringBuilder();
			sb.Append( "{" );
			for( int i = 0; i < this.Length; i++ )
			{
				sb.Append( "{" );
				for( int j = 0; j < this[i].Length; j++ )
				{				
					sb.Append( this[i][j] );		
					if( j == this.Length-1 )
						continue;
					sb.Append( "," ); //otherwise
				}
				sb.Append( "}" );
				if( i == this.Length-1 )
					continue;
				else
					sb.Append( ", " );
			}
			sb.Append( "}" );
			return sb.ToString();
		}		

		private static string Prompt( string prompt ) {	//this is just for my convenience		
			Console.WriteLine( prompt );				//display the prompt
			return Console.ReadLine();					//get the text and return it
		}
	}
}
